<?php
/**
 * @author		João Batista Neto
 * @brief		Classes e interfaces utilitárias
 * @package		rpo.util
 */

require_once 'rpo/base/Object.php';
require_once 'rpo/util/ObjectIterator.php';
require_once 'rpo/util/MapReduce.php';

/**
 * @brief	Interface para um armazenador de objetos
 * @class	ObjectStorage
 */
abstract class ObjectStorage extends Object implements ArrayAccess, Countable, IteratorAggregate, MapReduce, Serializable {
	/**
	 * @brief	Instância de ObjectIterator
	 * @var		ObjectIterator
	 */
	private $storage;

	/**
	 * @brief	Constroi o armazenador de objetos
	 */
	public function __construct(){
		$this->storage = $this->createObjectIterator();

		if ( !$this->storage->getClass()->implementsInterface( 'MapReduce' ) ){
			throw new UnexpectedValueException( 'O objeto retornado pelo método createObjectIterator() precisa implementar MapReduce' );
		}
	}

	/**
	 * @brief	Verifica se um objeto é aceitável antes de adicioná-lo
	 * @param	$object IObject
	 * @return	boolean
	 */
	abstract protected function accept( IObject $object );

	/**
	 * @brief	Adiciona um novo objeto
	 * @param	$object IObject
	 */
	public function append( IObject $object ){
		$this->storage->append( $object );
	}

	/**
	 * @brief	Recupera o total de itens do Iterator
	 * @return	integer
	 * @see		Countable::count()
	 */
	public function count(){
		return $this->storage->count();
	}

	/**
	 * @brief	Cria uma instância do objeto Iterator
	 * @details	Esse método é útil em casos em que é necessário modificar o tipo
	 * do objeto Iterator.
	 * @return	ObjectIterator
	 */
	abstract protected function createObjectIterator();

	/**
	 * @brief	Recupera uma cópia do Iterator
	 * @return	ObjectIterator
	 * @see		IteratorAggregate::getIterator()
	 */
	public function getIterator(){
		return clone $this->storage;
	}

	/**
	 * @brief	Efetua o mapeamento de um par chave-valor.
	 * @param	$key mixed Chave do mapeamento
	 * @param	$value mixed Valor do mapeamento
	 */
	public function map( $key , $value ){
		throw new BadMethodCallException( 'Opz...' );
	}

	/**
	 * @brief	Efetua uma operação de mapeamento e redução em todos os itens.
	 * @param	$map MapStep Objeto de mapeamento
	 * @param	$reduce ReduceStep Objeto de redução
	 * @return	Iterator
	 * @see		MapReduce.emit()
	 * @throws	LogicException Se o método MapReduce::map() não for chamado ao menos uma vez
	 */
	public function mapReduce( MapStep $map , ReduceStep $reduce ){
		$this->storage->mapReduce( $map , $reduce );
	}

	/**
	 * @brief	Verifica se um índice existe no Iterator
	 * @return	boolean
	 * @see		ArrayAccess::offsetExists()
	 */
	public function offsetExists( $offset ){
		return $this->storage->offsetExists( $offset );
	}

	/**
	 * @brief	Recupera um item do Iterator
	 * @return	IObject
	 * @see		ArrayAccess::offsetGet()
	 * @throws	OutOfBoundsException Se o offset especificado não existir
	 */
	public function offsetGet( $offset ){
		if ( !$this->storage->offsetExists( $offset ) ){
			throw new OutOfBoundsException( 'O offset não existe' );
		} else {
			return $this->storage->offsetGet( $offset );
		}
	}

	/**
	 * @brief	Define um novo item no Iterator utilizando um offset absoluto
	 * @see		ArrayAccess::offsetSet()
	 * @throws	UnexpectedValueException Se o novo valor não implementar IObject
	 */
	public function offsetSet( $offset , $object ){
		if ( $object instanceof IObject ){
			$this->storage->offsetSet( $offset , $object );
		} else {
			throw new UnexpectedValueException( sprintf( 'Era esperado um objeto, %s foi dado.' , gettype( $object ) ) );
		}
	}

	/**
	 * @brief	Remove um item do Iterator
	 * @see		ArrayAccess::offsetUnset()
	 * @throws	OutOfBoundsException Se o offset especificado não existir
	 */
	public function offsetUnset( $offset ){
		if ( !$this->storage->offsetExists( $offset ) ){
			throw new OutOfBoundsException( 'O offset não existe' );
		} else {
			$this->storage->offsetUnset( $offset );
		}
	}

	/**
	 * @brief	Efetua a redução de um valor relacionado com uma chave.
	 * @param	$key mixed Chave do mapeamento
	 * @param	$value mixed Valor reduzido
	 */
	public function reduce( $key , $value ){
		throw new BadMethodCallException( 'Opz...' );
	}

	/**
	 * @brief	Serializa o objeto
	 * @return	string
	 * @see		Serializable::serialize()
	 */
	public function serialize(){
		return $this->storage->serialize();
	}

	/**
	 * @brief	Retorna a string serializada ao objeto original
	 * @see		Serializable::unserialize()
	 */
	public function unserialize( $serialized ){
		$this->storage->unserialize( $serialized );
	}
}